<?php
/**
 * json decoder & encoder
 *
 * wrapper for json_encode/decode functions.
 * have a json parser for non-standart js
 *
 * @version:	0.1
 * @author:	alex 'alex_ez' yaroshevich <qfox@ya.ru>
 * @date:	20 oct 2008
 * @todo:	optimization, change parsing algorithms
 *
 */

class json {

static function decode( $s )
{
	if( !is_string( $s ) ) return null;
	
	$s = trim( $s );
	if( is_callable( 'json_decode' ) )
	{
		$_r = json_decode( preg_replace( "@^'|'$@", '"', $s ), 1 );
		if( !is_null( $_r ) ) return $_r;
	}
	
	if( $_r = self::parseScalar( $s ) ) return $_r;
	
	$ch = mb_substr( $s, 0, 1 );
	switch( $ch ) {
	case '[':
		$_sub = mb_substr( $s, 0 );
		$_end = self::findEnd( $_sub );
		$_sub = mb_substr( $_sub, 1, $_end-2 );
		$_r = self::parseArray( $_sub );
		break;
	case '{':
		$_sub = mb_substr( $s, 0 );
		$_end = self::findEnd( $_sub );
		$_sub = mb_substr( $_sub, 1, $_end-2 );
		$_r = self::parseObject( trim( $_sub ) );
		break;
	default:
		$_r = "?: ".$s;
	}
// end of parser
	
	return $_r;
}

static function parseScalar( $s )
{
	$s = trim( $s );
	if( $s === '' || $s === 'null' ) return "null";
	if( is_numeric( $s ) ) return (( strpos( $s, '.' ) ) !== false ? floatval( $s ) : intval( $s ));
	if( $r=self::parseString( $s ) ) return $r;
	return null;
}

static function parseString( $s )
{
	$s = trim( $s );
	if( preg_match('@(^"[^"]+"$)|(^\'[^\']+\'$)@',$s) ) return stripslashes(trim($s,"\"\'"));
	if( preg_match('@(^[a-z_][a-z0-9_]*$)@',$s) ) return $s;
	return null;
}

static function parseKey( $s )
{
	$s = trim( $s );
	if( $r=self::parseString( $s ) ) return $r;
	if( preg_match('@(^[a-z0-9_]*$)@',$s) ) return $s;
	return null;
}



static function parseObject( $s )
{
	$_r = array();
	//echo "! ".addcslashes($s,"\r\n\t")."\n";
	$s = trim( $s );
	for( $l=mb_strlen($s),$i=0; $i<$l; $i++ )
	{
		// skip delimiter and spaces
		$ch = mb_substr( $s, $i, 1 );
		if( $ch == ',' || trim( $ch ) === '' ) continue;
		
		$_sub = mb_substr( $s, $i );
		$_end = self::findEnd( $_sub );
		$_k = self::parseKey( mb_substr( $s, $i, $_end ) );
		//echo ": $i; '".addcslashes(mb_substr($_sub, 0, $_end),"\r\n\t")."|".addcslashes(mb_substr($_sub,$_end),"\r\n\t")."'\n";
		$i += $_end;
		
		while( mb_substr( $s, $i-1, 1 ) != ':' && $i<$l ) $i++;
		
		$_sub = mb_substr( $s, $i );
		//echo "> '".addcslashes(mb_substr( $s, $i ),"\r\n\t")."'\n";
		$_end = self::findEnd( $_sub );
		$_v = mb_substr( $s, $i, $_end );
		$i += $_end;
		
		$_r[$_k] = json::decode( $_v );
		//echo "\ti:$i;\te:$_end;  k: ".addcslashes($_k,"\r\n\t").", \tv: ".addcslashes($_v,"\r\n\t")."; sub: <".addcslashes($_sub,"\r\n\t").">;\n\ts: <".addcslashes(mb_substr( $s, 0, $i )."^".mb_substr( $s, $i ),"\n\r\t").">\n";
	}
	//print_r( $_r );
	return $_r;
}

static function parseArray( $s )
{
	$_r = array();
	//echo "! ".addcslashes($s,"\r\n\t")."\n";
	for( $l=mb_strlen($s),$i=0; $i<$l; $i++ )
	{
		// skip delimiter and spaces
		$ch = mb_substr( $s, $i, 1 );
		if( $ch == ',' || trim( $ch ) === '' ) continue;
		
		$_sub = mb_substr( $s, $i );
		$_end = self::findEnd( $_sub );
		//$_kend = mb_strpos( $s, ',', $i+$_end );
		//echo "\t$_end -\t$_send\n";
		//$_end = self::findEnd( $_sub, false, ',' );
		//self::decode( );
		$_v = mb_substr( $s, $i, $_end );
		
		$_r[] = json::decode( trim($_v) );
		//echo "i:$i;\te:$_end; \tv: ".addcslashes($_v,"\r\n\t")."; sub: <".addcslashes($_sub,"\r\n\t").">"."\n\t<".addcslashes(mb_substr( $s, 0, $i )."|".mb_substr( $s, $i ),"\n\r\t").">\n";
		
		$i += $_end;
		
		//if( $i > 15 ) die;
	}
	//print_r( $_r );
	return $_r;
}

/**
 * find end of block
 * 
 * string $s	our string
 * bool   $sqm	skip quote blocks mode
 * string $sc	stop character
 * 
 */
static function findEnd( $s, $sqm = false, $sc = null )
{
	$_s = $s; $s=ltrim( $s );
	$left = mb_strlen( $_s ) - mb_strlen( $s );
	$fc = mb_substr( $s, 0, 1 );
	//echo $s;
	
	if( !mb_strlen( $s ) ) return $left;
	
	// fetch closing character, if need
	$_fc2sc = array('"'=>'"',"'"=>"'",'{'=>'}','['=>']','('=>')','<'=>'>');
	if( is_null( $sc ) )
	{
		$sc = @$_fc2sc[ $fc ]; // stop character
		if( isQuote($sc) ) { $sqm = true; }
		if( !$sc ) $sc = ",:";
	}
	
	$parenthesis = ( isset( $_fc2sc[ $sc ] ) || in_array( $sc, $_fc2sc ) );
	//echo "\nfc: $fc, sc: $sc, sqm: $sqm, parenthesis: $parenthesis\n\n";
	
	// parenthesis validation.
	$sq = isQuote( $fc ) ? $fc : false;
	$cc = $fc;
	
	for( $l=mb_strlen($s),$j=1,$p=1; $p&&$j<$l; $j++ )
	{
		$pc=$cc; $cc=mb_substr($s,$j,1);
		//$pp=$p;
		
		if( !$sqm && isQuote($cc) )
		{
			$_nos = false; // no open backslash
			if( $pc !== "\\" ) $_nos = true;
			if( !$_nos ) { for( $_j=$j; $_j>1; $_j-- ) if( mb_substr($s,$_j-1,1) != "\\" ) break; $_nos=($j-$_j+1)%2; }
			if( $_nos && $sq===false ) { $sq=$cc; } elseif( $_nos && $sq==$cc ) { $sq=false; }
		}
		elseif( $sqm )
		{
			$_nos = false; // no open backslash
			if( $pc !== "\\" ) $_nos = true;
			if( !$_nos ) { for( $_j=$j; $_j>1; $_j-- ) if( mb_substr($s,$_j-1,1) != "\\" ) break; $_nos=($j-$_j+1)%2; }
			if( !$_nos ) continue;
		}
		
		if( !$parenthesis && $sq === false && strpos( $sc, $cc ) !== false )
			$p = 0;
		elseif( !$sqm && $sq === false )
			$p += ($cc===$sc) ? (-1) : ( ($cc===$fc) ? 1 : 0 );
		elseif( $sqm )
			$p -= ($cc===$sc);
		
		//echo "\ti: ".($sq?'-':'')."$j\tpp: $pp, p: $p, pc: $pc, cc: $cc,\tsq: $sq, cc=': ".isQuote($cc).", pc!=\\: ".($pc != "\\")."\n";
	}
	
	if( !$parenthesis && strpos( $sc, $cc ) !== false ) $j--;
	
	// parenthesis parse error. errors with unclosed parenthesis
	if( $parenthesis && $p ) die( "unclosed parenthesis ".$fc."; s: ".$s."; pos: ".$j."\n" );
	
	return $j+$left;
}

static function encode( $a )
{
	if( is_callable( 'json_encode' ) ) return json_encode( $a );
	
	if( is_null( $a ) ) return 'null';
	if( is_string( $a ) ) return '"'.addslashes( $a ).'"';
	if( is_numeric( $a ) && !is_float( $a ) ) return intval( $a );
	if( is_numeric( $a ) ) return floatval( $a );
	if( is_bool( $a ) ) return !!$a;
	if( is_resource( $a ) ) return '\"resource\"';
	if( is_array( $a ) || is_object( $a ) )
	{
		$r = array( );
		foreach( $a as $k => $v )
		{
			$r[] = json::encode( $k ).':'.json::encode( $v );
		}
		return '{'.join(',',$r).'}';
	}
	return json::encode( ''.$a );
}
}

	function isDQuote( $c ) { return $c=="'"; }
	function isSQuote( $c ) { return $c=='"'; }
	function isQuote( $c ) { return isDQuote($c) || isSQuote($c); }

?>